#include "udpSocketServer.h"
#include "helper.h"
#include <algorithm>
#include <sstream>
#include <errno.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <string.h>
#include "randomNumGenerator.h"


UDPSocketServer::UDPSocketServer( const std::string ports, const std::string listenAddress ):listenAddress_( listenAddress )
{
	std::vector< std::string > elems;
	split( ports, ',', elems );
	for( uint32_t i = 0; i < elems.size(); ++i )
	{
		std::vector< std::string > range;
		split( elems[i], '-', range );
		if( range.size() == 2 )
		{
			//is a range
			int begin = ss_atoi< int >( range[0] );
			int end = ss_atoi< int >( range[1] );
			for( int k = begin; k <= end; ++k )
			{ //create sockets
				int socket = createSocket( k );
				sockets_[k] = socket;
			}
		}
		else if( range.size() == 1 )
		{
			//is a single port
			int port = ss_atoi< int >( range[0] );
			int socket = createSocket( port );
			sockets_[ port ] = socket;
		}
		else
		{
			//error 
			std::cerr << "Problem parsing port range for UDP Socket Server" << std::endl;
			continue;
		}
	}
	messageQueues_.push_back( NULL ); //default registration map returns 0 si the first place can't be used
}

UDPSocketServer::~UDPSocketServer()
{

}

std::vector<std::string> & UDPSocketServer::split(const std::string &s, char delim, std::vector<std::string> &elems)
{
    std::stringstream ss(s);                                                                       
    std::string item;                                                                              
    while(std::getline(ss, item, delim)) {                                                         
        elems.push_back(item);                                                                     
    }                                                                                              
    return elems;                                                                                  
}      

int UDPSocketServer::createSocket( const uint16_t &port )
{
	struct sockaddr_in6 listenAddress;
	int socket;
	memset( (char*) &listenAddress, 0, sizeof( listenAddress ) ); //initialize
	#ifdef SIN6_LEN
	listenAddress.sin6_len = sizeof( listenAddress );
	#endif

	listenAddress.sin6_port = htons( port );
	listenAddress.sin6_addr   = in6addr_any;
	listenAddress.sin6_family = AF_INET6;
	socket = ::socket( AF_INET6, SOCK_DGRAM, 0 );
	if( socket < 0 )
	{
		throw UDPError( "Failed to create socket" );
	}

	//bind socket
	if( bind( socket, (struct sockaddr*) &listenAddress, sizeof( listenAddress ) ) < 0 )
	{
		//error
		std::cerr << "ERROR: binding socket " << port << std::endl;
		printError();
		exit( 2 );
	}

	return socket;

}

void UDPSocketServer::printError()
{
	switch( errno )
	{
		case EACCES:
			std::cerr << "The requested address is protected, and the current user has inadequet premission to access it" << std::endl;
			break;
		case EADDRINUSE:
			std::cerr << "The specified address is already in use" << std::endl;
			break;
		case EADDRNOTAVAIL: 
			std::cerr << "The specified address is not available on the local machine" << std::endl;
			break;
		case EAFNOSUPPORT:
			std::cerr << "Address is not valid for the address family of socket" << std::endl; 
			break;
		case EBADF:
			std::cerr << "Socket is not a vailid file descriptor" << std::endl;
			break;
		case EDESTADDRREQ:
			std::cerr << "Socket is a null pointer" << std::endl;
			break;
		case EFAULT:
			std::cerr << "The address parameter is not in the valid part of the user address space" << std::endl;
			break;
		case EINVAL:
			std::cerr << "socket is already bound to an address and the protocol does not support binding to a new address.  Alternatively, socket may have been shut down." << std::endl;
			break;
		case ENOTSOCK:
			std::cerr << "socket does not refer to a socket." << std::endl;
			break;
		case EOPNOTSUPP: 
			std::cerr << "socket is not of a type that can be bound to an address." << std::endl;
			break;
		case EIO:
			std::cerr << "An I/O error occurred while making the directory entry or allocating the inode." << std::endl;
			break;
		case EISDIR:
			std::cerr << "An empty pathname was specified." << std::endl;
			break;
		case ELOOP:
			std::cerr << "Too many symbolic links were encountered in translating the pathname.  This is taken to be indicative of a looping symbolic link." << std::endl;
			break;
		case ENAMETOOLONG:
			std::cerr << "A component of a pathname exceeded {NAME_MAX} characters, or an entire path name exceeded {PATH_MAX} characters." << std::endl;
			break;
		case ENOENT:
			std::cerr << "A component of the path name does not refer to an existing file." << std::endl;
			break;
		case ENOTDIR:
			std::cerr << "A component of the path prefix is not a directory." << std::endl;
			break;
		case EROFS:
			std::cerr << "The name would reside on a read-only file system." << std::endl;
			break;
		default:
			std::cerr << "UNKNOWN ERROR: " << errno << std::endl;
	}

}

int UDPSocketServer::registerThread( UDPMessageQueue *queue )
{
	MutexLocker lock( messageQueueMutex_ );
	messageQueues_.push_back( queue );
	return messageQueues_.size() - 1;
}

void UDPSocketServer::unregisterThread( const int &threadid )
{
	MutexLocker lock( messageQueueMutex_ );
	std::vector< UDPMessageQueue* > newQueue;
	for( int i = 0; i < static_cast<int>( messageQueues_.size() ); ++i )
	{
		if( i == threadid )
			continue;
		newQueue.push_back( messageQueues_[i] );
	}
	messageQueues_ = newQueue;
}

int UDPSocketServer::requestSocket( const int &threadid, const int &port )
{
	uint16_t portnum = 0;
	if( port == RANDOM_PORT )
	{
		portnum = randomPort();
	}
	else
	{
		portnum = static_cast< uint16_t >( port );
		if( sockets_.find( port ) == sockets_.end() )
		{
			return PORT_NOT_ALLOCATED;
		}
		if( port < 0 || port > 65535 )
		{
			return PORT_NOT_LEGAL;
		}
		
	}
	MutexLocker lock2( registrationMutex_ );
	if( registrations_.find( port ) != registrations_.end() )
		return PORT_IN_USE;

	registrations_[portnum] = threadid;
	return portnum;
}

void UDPSocketServer::releaseSocket( const uint16_t &port )
{
	MutexLocker lock2( registrationMutex_ );
	registrations_.erase( port );
}

void UDPSocketServer::sendData( const UDPMessage &message )
{
	int socket = sockets_[message.source().portNum() ]; 
	if( socket <= 0 )
		throw UDPError( "Socket with source port doesn't exist" );

	int sent = sendto( socket, message.data().buffer(), message.data().size(), 0,
			(sockaddr*) message.destination().sockaddr(), message.destination().sockaddrSize() );

	if( sent < 0 )
	{
		printError();
		throw UDPError( "Error Sending Data" );
	}

	if( sent != message.data().size() )
	{
		std::cerr << "Didn't send all the bits..." << std::endl;
	}
}

void UDPSocketServer::runListener()
{
	/***********************************************
	 * Listen for connections on all of the sockets
	 * Pass all message on to intel queue and any
	 * registered queue
	 * *********************************************/

	uint8_t buffer[ MAX_UDP_DNS_PACKET_SIZE ];
	while( true )
	{
		int maxSocket = 0;
		fd_set sockets;
		FD_ZERO( &sockets );
		std::map< uint16_t, int >::iterator itr;
		for( itr = sockets_.begin(); itr != sockets_.end(); ++itr )
		{
			FD_SET( itr->second, &sockets );
			if( itr->second + 1 > maxSocket )
				maxSocket = itr->second + 1;
		}
	
		if( select( maxSocket, NULL, &sockets, NULL, NULL ) < 0 )
		{
			//error
			printError();
			throw UDPError( "Error with select" );
		}

		for( itr = sockets_.begin(); itr != sockets_.end(); ++itr )
		{
			if( FD_ISSET( itr->second, &sockets ) )
			{
				//read
				struct sockaddr_in6 fromAddr;
				socklen_t fromLen = sizeof( fromAddr );
				int size = recvfrom( itr->second, &buffer, MAX_UDP_DNS_PACKET_SIZE, 0, (sockaddr*) &fromAddr, &fromLen );
				if( size < 0 ) 
				{
					printError();
					throw UDPError( "Error receiving Data" );
				}
				UDPMessage message;
				message.setSource( fromAddr );
				message.setData( buffer, size );
				IPv6Address destination;
				destination.setAddress( listenAddress_ );
				destination.setPort( itr->first );
				destination.setFamily( AF_INET6 );
				message.setDestination( destination );
				intelMessageQueue_->push( message );
				MutexLocker lock(registrationMutex_);
				if( registrations_[ itr->first ] > 0 ) //there is a registration
				{
					MutexLocker lock( messageQueueMutex_ );
					messageQueues_[ registrations_[ itr->first ] ]->push( message );
				}
			}
		}
	}

}

/* Not needed sent in the send function.
void UDPSocketServer::runSender() 
{
	//pop send queue and send stuff
	while( true )
	{
		UDPMessage message = sendQueue_.pop();
		//find the socket 
		uint16_t port = messge.source().portNum();
		int socket = sockets_[ port ];
		

		sendto( socket, message.data().buffer()
	}

}
*/

void UDPSocketServer::setIntelMessageQueue( UDPMessageQueue *queue )
{
	intelMessageQueue_ = queue;
}

uint16_t UDPSocketServer::randomPort()
{
	RandomNumberGenerator g;
	uint16_t randomIndex = g.generate< uint16_t >();
	int range = sockets_.size();
	randomIndex %= range;
	std::map< uint16_t, int >::iterator itr;
  itr = sockets_.begin();
	for( int i = 0; i < randomIndex; ++i )
	{
		++itr;
	}

	return itr->first;
}
